#!/usr/bin/env python3
import os
import shutil
import subprocess
import sys
import threading
import time
from tkinter import filedialog
import tkinter.font as tkfont
import requests
import tkinter as tk
import ttkbootstrap as ttk
# from tkinter import ttk
from tkinter import messagebox
from tkinter.scrolledtext import ScrolledText
import py.chk_connect as chk_connect
import py.update as updater
import py.qrutils as qr
import webbrowser

AUTHOR = "某贼 affggh"
VERSION = "基于 批处理 2.0.2"

UI_THEME = 'vapor'
UI_CHECK_UPDATE = True      # 是否检查更新
UI_OUTPUT_LOG = True         # 关闭时输出TEXT控件日志

introduce = '''\
    viQOO_Py 是基于python脚本编写的帮助
    大家解锁玩机的一个图形工具，脚本来自
    某贼整合的工具viQOO批处理工具。
    如果你花钱买到了这个工具你就是大傻逼
'''


os.chdir(os.path.dirname(sys.argv[0])) # prefix

def addExecPath(addpath):
    envpath = os.getenv('PATH')
    execpath = os.path.abspath(addpath)
    os.putenv('PATH', execpath+";"+envpath)

addExecPath(os.path.dirname(sys.argv[0])+os.sep+"bin")  # Add bin into PATH

def downloadFile(url, fileToSave):
    def p(now, total):
        return int((now/total)*100)
    file = fileToSave
    chunk_size = 512
    log("开始下载文件...")
    r = requests.get(url, stream=True)
    total_size = int(r.headers['content-length'])
    now = 0
    progressbar['maximum'] = 100
    with open(file, 'wb') as f:
        for chunk in r.iter_content(chunk_size=chunk_size):
            if chunk:
                before = now
                f.write(chunk)
                now += chunk_size
                if now > before:
                    # print("下载进度 [%s/100]" %progress(now, total_size), end='\r')
                    progress.set(p(now, total_size))
    log("下载完成!")
    progress.set(0)

def releases_dump_browser_download_url(url ,name, assets): # only support gitee
    # example https://gitee.com/api/v5/repos/mouzei/viqoo/releases
    r = requests.get(url).json()
    for i in r:
        if i['name'] == name:
            for j in i['assets']:
                if j['name'] == assets:
                    return j['browser_download_url']
    return False

def runcmd(cmd):
    try:
        ret = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        for i in iter(ret.stdout.readline, b''):
            text.insert('end', i.strip().decode("UTF-8", errors='ignore') + "\n")
            text.update()
            text.yview('end')
    except subprocess.CalledProcessError as e:
        for i in iter(e.stdout.readline,b''):
            text.insert('end', i.strip().decode("UTF-8", errors='ignore') + "\n")
            text.update()
            text.yview('end') 

def log(s):
    text.insert('end', s+'\n')
    text.update()
    text.yview('end')

def tlog(s):
    logtime = time.strftime("[%H:%M:%S]", time.time())
    text.insert('end', logtime+s+'\n')
    text.update()
    text.yview('end')

def chk_status():
    status = chk_connect.chk_status()
    if status:
        log("设备状态 : [%s]" %status)
    else:
        log("设备未找到!")

class myApp(ttk.Frame):
    def __init__(self, parent):
        ttk.Frame.__init__(self)

        # Setup vars
        self.model = tk.StringVar()
        self.unlockMethod = tk.StringVar()
        self.filename = tk.StringVar()

        self.setup_widgets()
    def talk_group(self):
        messagebox.showinfo(title="讨论组", message="QQ 群: 735532054")
    
    def unlockBootloader(self):
        def __unlock(type):
            def chk_unlock():
                s = subprocess.getoutput("fastboot getvar all")
                if "bootloader" in s:
                    if "unlocked:yes" in s:
                        log("设备已经解锁!")
                        return True
                    else:
                        log("设备未解锁...")
                        return False
                else:
                    log("Fastboot模式变量获取失败...")
                    return False
            def chk_part_type():
                s = subprocess.getoutput("fastboot getvar all")
                part_type = False
                if not "partition-type:super:" in s:
                    part_type = "normal"
                else:
                    part_type = "super"
                log("设备分区类型 : "+part_type)
                return part_type

            def chk_product():
                s = subprocess.getoutput("fastboot getvar all")
                if s.find("product:") > 0:
                    for i in s.splitlines():
                        if i.split(" ")[1].split(":")[0] == "product":
                            return i.split(" ")[1].split(":")[1]
                else:
                    return False

            def pre_chk():
                if chk_connect.chk_status()=="device":
                    log("重启至 bootloader")
                    cmd = "adb reboot bootloader"
                    runcmd(cmd)
                    log("等待设备重启进入fastboot模式")
                    if chk_connect.loopUntil("fastboot"):
                        device_product = chk_product()
                        log("设备 product : "+device_product)
                        if not chk_unlock():
                            part_type = chk_part_type()
                            if not part_type == "normal":
                                log("重启至fastbootd...")
                                runcmd("fastboot reboot fastboot")
                                time.sleep(1)
                                if chk_connect.loopUntil("fastboot"):
                                    log("重启至fastbootd完成...")
                                    return True
                                else:
                                    log("检查超时 ...")
                                    return False
                            return True
                        else:
                            return False
                    else:
                        log("超时 ...")
                        return False
                else:
                    log("设备不在此状态[device]!")

            def decompressVendor():
                if os.access("bin/vendor.7z", os.F_OK):
                    updater.decompressFile("bin/vendor.7z")
                    return True
                else:
                    log("vendor.7z 丢失...")
                    return False

            log("type = %s" %type)
            
            if type == 'xda':
                if messagebox.askyesno(title="请检查", message="请确认：\n" \
                                                                     "你设备开发者设置中 [oem unlock] 为开启\n" \
                                                                     "确保usb调试开启并给予授权..."):
                    if not pre_chk():
                        return False
                    log("解压 vendor.7z 到 vendor.img...")
                    if not decompressVendor():
                        cmd = "fastboot flash vendor vendor.img"
                        if not "FAILED" in subprocess.getoutput(cmd):
                            cmd = "fastboot_vivo vivo_bsp unlock_vivo"
                            if not "FAILED" in subprocess.getoutput(cmd):
                                return True
                            else:
                                log("解锁失败....")
                        else:
                            log("vendor分区刷入失败...")
                    else:
                        return False
                else:
                    log("取消解锁...")
            if type == 'mtk':
                if os.access("mtkclient-gui/start.bat", os.F_OK):
                    log("mtkclient-gui 启动中...")
                    os.system("start mtkclient-gui"+os.sep+"start.bat")
                else:
                    log("mtkclient 文件不存在, 尝试下载...")
                    url = "https://gitee.com/api/v5/repos/mouzei/viqoo/releases"
                    downloadurl = releases_dump_browser_download_url(url, "mtkclient", "mtkclient-gui.7z")+"/mtkclient-gui.7z"
                    log("下载中...")
                    downloadFile(downloadurl, "mtkclient-gui.7z")
                    log("解压中...")
                    updater.decompressFile("mtkclient-gui.7z")
                    log("完成!")
                    log("删除 7z 压缩包...")
                    if os.access("mtkclient-gui.7z", os.F_OK):
                        os.remove("mtkclient-gui.7z")
                    os.system("start mtkclient-gui"+os.sep+"start.bat")
            if type == 'old':
                if not pre_chk():
                    return False
                if chk_connect.chk_status() == "fastboot":
                    cmd = "fastboot_vivo bbk unlock_vivo"
                    runcmd(cmd)
                    log("请在文本框中检查 是否有 'FAILD' 被打印出来...")
                else:
                    log("设备未连接...")
        def unlock(type):
            th = threading.Thread(target=__unlock, args=[type])
            th.daemon = True
            th.start()
            win.destroy()
        log("选择你要解锁的方式...")
        win = tk.Toplevel(self)
        win.title("选择解锁方式")
        win.geometry("320x120")
        win.update()
        win.minsize(win.winfo_width(), win.winfo_height())
        x_cordinate = int((win.winfo_screenwidth() / 2) - (win.winfo_width() / 2))
        y_cordinate = int((win.winfo_screenheight() / 2) - (win.winfo_height() / 2))
        win.geometry("+{}+{}".format(x_cordinate, y_cordinate))
        button1 = ttk.Button(win, style="secondary", width=300, command=lambda:unlock("xda"), text="通用解锁(来自xda)")
        button2 = ttk.Button(win, style="secondary-outline", width=300, command=lambda:unlock("mtk"), text="启动 MTK client(可自动下载)")
        button3 = ttk.Button(win, style="secondary-outline", width=300, command=lambda:unlock("old"), text="老设备解锁")
        button1.pack(side='top', padx=10, pady=5, fill='x', expand=True)
        button2.pack(side='top', padx=10, pady=5, fill='x', expand=True)
        button3.pack(side='top', padx=10, pady=5, fill='x', expand=True)

    def __selectModel(self):
        def selectDevice():
            if not combobox.get() == '':
                self.model.set(combobox.get())
                log("选择 设备为 [%s]" %self.model.get())
            else:
                self.model.set("")
                log("设备未选择!")
            win.destroy()
        log("从网络在线加载设备清单...")
        modelList = updater.getModelSupportList()
        win = ttk.Toplevel(self)
        win.title("选择你的设备")
        win.geometry("320x120")
        win.update()
        win.minsize(win.winfo_width(), win.winfo_height())
        x_cordinate = int((win.winfo_screenwidth() / 2) - (win.winfo_width() / 2))
        y_cordinate = int((win.winfo_screenheight() / 2) - (win.winfo_height() / 2))
        win.geometry("+{}+{}".format(x_cordinate, y_cordinate))
        combobox = ttk.Combobox(win)
        if len(modelList)>1:
            combobox['value'] = modelList
        combobox.pack(side="top", expand=True, fill='x', anchor='center', padx=10, pady=5)
        button = ttk.Button(win, width=8, text="确认", command=selectDevice)
        button.pack(side='bottom', anchor='e', padx=10, pady=10)

    def selectModel(self):
        th = threading.Thread(target=self.__selectModel)
        th.start()

    def __usefulTools(self):
        log("实用工具")
        win = tk.Toplevel(self)
        win.title("选择一个功能")
        # win.geometry("320x120")
        win.update()
        win.minsize(win.winfo_width(), win.winfo_height())
        x_cordinate = int((win.winfo_screenwidth() / 2) - (win.winfo_width() / 2))
        y_cordinate = int((win.winfo_screenheight() / 2) - (win.winfo_height() / 2))
        win.geometry("+{}+{}".format(x_cordinate, y_cordinate))
        button1 = ttk.Button(win, command=lambda:webbrowser.open("https://share666-my.sharepoint.com/:f:/g/personal/share666_share666_onmicrosoft_com/EnycEZAcvEFPtvVqVf18PsYBMuf2NtQ7orqzmuKbh0N-LA?e=7t3ub7"), text="AFTool（vivo售后刷机工具，此版本亲测可用）")
        button2 = ttk.Button(win, command=lambda:webbrowser.open("https://share666-my.sharepoint.com/:f:/g/personal/share666_share666_onmicrosoft_com/EnycEZAcvEFPtvVqVf18PsYBMuf2NtQ7orqzmuKbh0N-LA?e=7t3ub7"), text="vivo系统降级工具")
        button3 = ttk.Button(win, command=lambda:qr.topQrImage(self, "https://syxz.lanzoub.com/ih8z4062ethe"), text="Magisk24.3 APP")
        button4 = ttk.Button(win, command=lambda:qr.topQrImage(self, "https://syxz.lanzoub.com/ir67O062h0ef"), text="MagiskAlpha APP")
        button5 = ttk.Button(win, command=lambda:qr.topQrImage(self, "https://syxz.lanzoub.com/isCaB062ehni"), text="Magisk24.3 APP")
        button6 = ttk.Button(win, command=lambda:webbrowser.open("https://syxz.lanzoub.com/i0t0V063oggb"), text="安装电脑刷机驱动")

        button1.pack(side='top', padx=10, pady=5, fill='x', expand=True)
        button2.pack(side='top', padx=10, pady=5, fill='x', expand=True)
        button3.pack(side='top', padx=10, pady=5, fill='x', expand=True)
        button4.pack(side='top', padx=10, pady=5, fill='x', expand=True)
        button5.pack(side='top', padx=10, pady=5, fill='x', expand=True)
        button6.pack(side='top', padx=10, pady=5, fill='x', expand=True)

    def usefulTools(self):
        th = threading.Thread(target=self.__usefulTools)
        th.daemon = True
        th.start()

    def __imgFlasher(self):
        log("镜像刷入工具...")
        if chk_connect.chk_status()=="fastboot":
            def choosefile():
                f = filedialog.askopenfilename(filetypes=[("Image", "*.img")])
                fname.set(os.path.abspath(f))
            def getcomboxvalue():
                pname = chk_connect.getFastbootPartitionName()
                combobox['value'] = pname
            def Flashit():
                if os.access(fname.get(), os.F_OK):
                    cmd = "fastboot flash %s \"%s\"" %(combobox.get(), fname.get())
                    runcmd(cmd)
                else:
                    log("文件不存在!")
                    return False
            fname = tk.StringVar()
            win = tk.Toplevel(self)
            win.title("选择按钮")
            win.geometry("320x120")
            win.update()
            win.minsize(win.winfo_width(), win.winfo_height())
            x_cordinate = int((win.winfo_screenwidth() / 2) - (win.winfo_width() / 2))
            y_cordinate = int((win.winfo_screenheight() / 2) - (win.winfo_height() / 2))
            win.geometry("+{}+{}".format(x_cordinate, y_cordinate))
            label = ttk.Label(win, text="选择的文件为:")
            entry = ttk.Entry(win, textvariable=fname, width=12)
            button = ttk.Button(win, text="选择文件", command=choosefile)
            label2 = ttk.Label(win, text="选择分区:")
            combobox = ttk.Combobox(win, width=12)
            log("获取处于fastboot模式下的设备分区列表...")
            getcomboxvalue()
            button2 = ttk.Button(win, text="刷入", command=Flashit)

            label.grid(row=0, column=0, padx=5, pady=5)
            entry.grid(row=0, column=1, padx=5, pady=5)
            button.grid(row=0, column=2, padx=5, pady=5)
            label2.grid(row=1, column=0, padx=5, pady=5)
            combobox.grid(row=1, column=1, padx=5, pady=5)
            button2.grid(row=1, column=2, padx=5, pady=5)

        else:
            log("未能找到处在fastboot模式下的设备....")
            return False
    def imgFlasher(self):
        th = threading.Thread(target=self.__imgFlasher)
        th.daemon = True
        th.start()

    def disableAVB(self):
        def chk_unlock():
            s = subprocess.getoutput("fastboot getvar all")
            if "bootloader" in s:
                if "unlocked:yes" in s:
                    log("设备已经解锁!")
                    return True
                else:
                    log("设备未解锁...")
                    return False
            else:
                log("Fastboot模式变量获取失败...")
                return False
        def abdev_flag():
            s = subprocess.getoutput("fastboot getvar all")
            abdev = False
            if "bootloader" in s:
                if "boot_a" in s:
                    abdev = True
                return abdev
            else:
                log("Fastboot模式变量获取失败...")
                return False
        if chk_connect.chk_status() == 'fastboot':
            if chk_unlock():
                if abdev_flag():
                    cmd = "fastboot --disable-verity --disable-verification flash vbmeta_ab bin\\vbmeta_empty.img"
                else:
                    cmd = "fastboot --disable-verity --disable-verification flash vbmeta bin\\vbmeta_empty.img"
                runcmd(cmd)
        else:
            log("请先将设备重启至Fastboot模式...")


    def setup_widgets(self):
        # master is grid
        logofont = tkfont.Font(family="Gabriola", size=50, weight=tkfont.BOLD)
        logo = ttk.Label(self, text='viQOO_Py', font=logofont, width=360, anchor="center")
        logo2 = ttk.Label(self, text='版本[%s] 作者[%s]' %(VERSION, AUTHOR), anchor="center")
        logo.pack(side='top', expand=True, fill='x')
        logo2.pack(side='bottom', expand=False, fill='x')
        button1 = ttk.Button(self, width=32, command=self.unlockBootloader, text="开始解锁BL")
        button2 = ttk.Button(self, width=32, command=lambda:log("请在批处理脚本中使用此功能..."), text="Root系统")
        button3 = ttk.Button(self, width=32, command=lambda:log("请在批处理脚本中使用此功能..."), text="移除Root")
        button3_1 = ttk.Button(self, width=32, command=self.disableAVB, text="关闭AVB2.0校验")
        button4 = ttk.Button(self, width=32, command=self.imgFlasher, text="刷入自定义分区")
        button5 = ttk.Button(self, width=32, command=self.usefulTools, text="实用工具")
        button6 = ttk.Button(self, width=32, command=self.selectModel, text="选择/更改你的型号")
        frame = ttk.Frame(self)
        button7 = ttk.Button(frame, width=10, command=self.talk_group,text="讨论群")
        button8 = ttk.Button(frame, width=20, command=chk_status, text="检查设备连接")
        
        # grid
        button1.pack(side='top', expand=True, fill='x', padx=10, pady=1)
        button2.pack(side='top', expand=True, fill='x', padx=10, pady=1)
        button3.pack(side='top', expand=True, fill='x', padx=10, pady=1)
        button3_1.pack(side='top', expand=True, fill='x', padx=10, pady=1)
        button4.pack(side='top', expand=True, fill='x', padx=10, pady=1)
        button5.pack(side='top', expand=True, fill='x', padx=10, pady=1)
        button6.pack(side='top', expand=True, fill='x', padx=10, pady=1)
        button7.pack(side='left', expand=True, fill='x', padx=(10,2), pady=1)
        button8.pack(side='left', expand=True, fill='x', padx=(0,10), pady=1)
        frame.pack(side='top', expand=True, fill='x')

if __name__ == '__main__':
    def onClosing():
        if messagebox.askyesno(title="viQoo 提醒您", message="要退出吗？"):
            # kill adb server
            if chk_connect.isAdbAlive():
                subprocess.check_output("adb kill-server", encoding='gb2312')
            # print("Exiting...")
            if UI_OUTPUT_LOG:
                with open("pylog.txt", 'w') as f:
                    f.write(text.get('1.0', 'end'))
            root.destroy()
    def __chk_update():
        updateSource = "https://viqoo.mouzei.repl.co/d/onedrive"
        try:
            current_version = updater.checkCurrentVersion()
            online_version = updater.checkOnlineVersion()
            if updater.compareCurrentVersionAndOnlineVersion(online_version, current_version):
                text.insert('end', "找到更新!\n")
                if messagebox.askyesno(title="检测到更新", message="是否更新？"):
                    # remove old files
                    try:
                        shutil.rmtree("bin")
                        os.remove("1.启动工具箱.bat")
                        os.remove("更新日志.txt")
                    except:
                        pass
                    downloadFile(updateSource+"/viQOO.7z", "update.7z")
                    text.insert('end', "解压 update.7z...")
                    updater.decompressFile("update.7z")
                    if os.access("update.7z", os.F_OK): # clean update.7z
                        os.remove("update.7z")
                    text.insert('end', "完成!\n")
                    __chk_update()
                else:
                    text.insert('end', "取消更新...\n")
                    text.insert('end', "警告 : 如果本地批处理脚本和python基于的批处理脚本不匹配可能会导致一些问题...\n")
            else:
                text.insert('end', "已经是最新!\n")
            if updater.selfVersionCheckout(VERSION.split(' ')[2]):
                messagebox.showinfo(title="python脚本需要更新!", message="请访问gitee来获取更新!")
            else:
                text.insert('end', "当前python脚本已是最新!\n")
        except requests.RequestException:
            text.insert('end', "连接失败!\n")
        if not chk_current():
            messagebox.showerror(title="文件检查失败", message="文件检查未通过, 请自行检查你当前的文件!")
            # root.destroy()     # close window
    def start_insert():
        text.insert('end', '''\
蛋总不要偷代码!
''')
    def chk_update():
        log("检查更新和文件...")
        text.insert('end', "检查更新 ...")
        th = threading.Thread(target=__chk_update)
        th.start()
    def chk_current():
        if os.access("bin/viqoo_config.bat", os.F_OK):
            currentversion = updater.checkCurrentVersion()
            log("批处理脚本当前版本 : " + currentversion)
        else:
            log("错误 ： 未能找到当前版本!")
            return False
        chk_list = ["bin/adb.exe",
                    "bin/AdbWinApi.dll",
                    "bin/AdbWinUsbApi.dll",
                    "bin/fastboot_vivo.exe",
                    "bin/fastboot.exe",
                    "bin/vendor.7z" ]
        for i in chk_list:
            text.insert('end', "检查 [%s] 是否存在..." %i)
            if os.access(i, os.F_OK):
                text.insert('end', "成功\n")
            else:
                text.insert('end', "失败!\n")
                return False
        return True

    def aboutus():
        win = tk.Toplevel(root)
        win.title("关于")
        win.geometry("320x280")
        win.update()
        win.minsize(win.winfo_width(), win.winfo_height())
        x_cordinate = int((win.winfo_screenwidth() / 2) - (win.winfo_width() / 2))
        y_cordinate = int((win.winfo_screenheight() / 2) - (win.winfo_height() / 2))
        win.geometry("+{}+{}".format(x_cordinate, y_cordinate))
        logofont = tkfont.Font(family="Gabriola", size=50, weight=tkfont.BOLD)
        logo = ttk.Label(win, text='viQOO_Py', font=logofont, width=360, anchor="center")
        logo2 = ttk.Label(win, text='版本[%s]\n作者[%s]' %(VERSION, AUTHOR), anchor="e")
        logo.pack(side='top', expand=True, fill='x')
        ttk.Label(win, text=introduce,anchor='center').pack(side='top', expand=True, fill='both')
        logo2.pack(side='top', expand=False, fill='x')

    def change_theme(theme):
        root.style.theme_use(theme)
        log("切换主题[%s]" %(theme))

    root = ttk.Window(themename=UI_THEME)
    root.title("viQOO_py")
    menubar = ttk.Menu(root)
    menubar_1 = ttk.Menu(menubar)
    for i in ttk.Style().theme_names():
        menubar_1.add_command(label=i, command=lambda x=i:change_theme(x))
    menubar.add_cascade(label="主题", menu=menubar_1)
    menubar.add_cascade(label="关于", command=aboutus)
    root['menu'] = menubar
    root.geometry("300x560")
    root.update()
    root.minsize(root.winfo_width(), root.winfo_height())
    x_cordinate = int((root.winfo_screenwidth() / 2) - (root.winfo_width() / 2))
    y_cordinate = int((root.winfo_screenheight() / 2) - (root.winfo_height() / 2))
    root.geometry("260x520+{}+{}".format(x_cordinate, y_cordinate))
    root.protocol("WM_DELETE_WINDOW", onClosing)
    app = myApp(root)
    global text
    global progressbar
    global progress
    progress = tk.DoubleVar(value=0)
    progressbar = ttk.Progressbar(root, variable=progress, mode="determinate")
    progressbar.pack(side='bottom', fill='x', padx=(10, 10), pady=(5, 5))
    app.pack(side='top', expand=True, fill='both', anchor="center")
    text = ttk.ScrolledText(root)
    text.pack(side='bottom', fill='x', padx=(10, 10), pady=(5, 5))
    start_insert()
    if UI_CHECK_UPDATE:
        chk_update()
    root.mainloop()